﻿using System;
using System.Collections.Generic;
using System.IO;

namespace DominionWar
{
    class Fleet
    {
        private readonly String FleetFilename;
        private String FleetName;
        private int NumberOfShips;
        private List<BaseShip> Ships = new List<BaseShip>();
        private List<BaseShip> ShipsDestroyed;

        private const String Version2Marker = "#2";

        public Fleet(String filename)
        {
            this.FleetFilename = filename;

            StreamReader fin = new StreamReader(filename);

            String firstLine = fin.ReadLine().Trim();
            if (firstLine == Version2Marker) // File is in the new format (V2)
            {
                SetupFleet_V2(fin);
                LoadFleet_V2(fin);
            }
            else // File is in the old format (V1)
            {
                SetupFleet_V1(fin, firstLine);
                LoadFleet_V1(fin);
            }

            String extraInput = fin.ReadToEnd();
            fin.Close(); // Make sure the file is closed before a potential error is thrown

            // If anything else is input, inform the user
            if (extraInput != null && extraInput.Trim() != "")
                throw new SimulationError("More ships than stated in " + filename);  
        }

        // Fleet setup based on version 1's file format
        private void SetupFleet_V1(StreamReader fin, String fleetName)
        {
            // First input file line
            if (fleetName == "")
            {
                fin.Close();
                throw new SimulationError("Missing fleet name in " + this.FleetFilename);
            }
            this.FleetName = fleetName;

            String line = fin.ReadLine();
            if (!Int32.TryParse(line, out this.NumberOfShips) || this.NumberOfShips <= 0)
            {
                fin.Close();
                throw new SimulationError("Invalid number of ships in " + this.FleetFilename);
            }
            // List<BaseShip> has already been initialised so nothing more needs to be setup
        }

        // Fleet setup based on version 2's file format
        private void SetupFleet_V2(StreamReader fin)
        {
            this.FleetName = fin.ReadLine().Trim();
            if (this.FleetName == "")
            {
                fin.Close();
                throw new SimulationError("Missing fleet name in " + this.FleetFilename);
            }
            this.Ships = new List<BaseShip>();
        }

        // Fleet load of ships based on version 1's file format
        private void LoadFleet_V1(StreamReader fin)
        {
            // And setup each ship listed in the remainder of the input file
            for (int i = 0; i < this.NumberOfShips; i++)
            {
                String name = fin.ReadLine();
                int shieldStrength = ReadNextInt(fin, "shield strength in ship class" + name);
                int regenerationRate = ReadNextInt(fin, "regen rate in ship class" + name);
                int hullStrength = ReadNextInt(fin, "hull strength in ship class" + name);
                int weaponBaseDamage = ReadNextInt(fin, "weapon base in ship class" + name);
                int weaponRandomDamage = ReadNextInt(fin, "weapon random in ship class" + name);

                this.Ships.Add(new Ship(name, shieldStrength, regenerationRate, hullStrength, weaponBaseDamage, weaponRandomDamage));
            }
        }

        private int ReadNextInt(StreamReader fin, String readErrorType)
        {
            int returnInt;
            bool parseSuccess = Int32.TryParse(fin.ReadLine(), out returnInt);

            if ((parseSuccess == false) || (returnInt <= 0))
            {
                fin.Close();
                throw new SimulationError("Invalid " + readErrorType + " in the file " + this.FleetFilename);
            }

            return returnInt;
        }

        // Fleet load of ships based on version 2's file format
        private void LoadFleet_V2(StreamReader fin)
        {
            int shipCount = 0; 
            while (true)
            {
                String nextShipClass = "";
                String nextShipCount = "";
                // Get the next line pair of ship information
                try
                {
                    nextShipClass = fin.ReadLine().Trim();
                    nextShipCount = fin.ReadLine().Trim();
                }
                catch (NullReferenceException) // EOF with a blank line before it
                {
                    if (fin.EndOfStream && nextShipClass == "" && nextShipCount == "")
                        // If EOF and nothing was read in, then all ship data has been loaded
                        break;
                }

                if (nextShipClass == "") // If not EOF and blank data, raise an error
                {
                    fin.Close();
                    throw new SimulationError(nextShipClass + " is not a valid ship class name in " + this.FleetFilename);
                }
                else if (nextShipCount == "") // If not EOF and blank data, raise an error
                {
                    fin.Close();
                    throw new SimulationError("Invalid number of ships in " + this.FleetFilename);
                }
                
                // No blanks, so try to parse the number
                int parsedNextShipCount;
                bool parseSuccess = Int32.TryParse(nextShipCount, out parsedNextShipCount);

                if ((parseSuccess == false) || (parsedNextShipCount <= 0))
                {
                    fin.Close();
                    throw new SimulationError("Invalid number of ships in " + this.FleetFilename);
                }
                shipCount += parsedNextShipCount;

                AddNewShips(nextShipClass, parsedNextShipCount);
            }
            this.NumberOfShips = shipCount;
        }

        private void AddNewShips(String shipClass, int count)
        {
            for (int i = 0; i < count; i++)
            {
                switch (shipClass)
                {
                    case "Akira":
                        this.Ships.Add(new Akira());
                        break;
                    case "Attack Ship":
                        this.Ships.Add(new AttackShip());
                        break;
                    case "Battle Cruiser":
                        this.Ships.Add(new BattleCruiser());
                        break;
                    case "Bird of Prey":
                        this.Ships.Add(new BirdofPrey());
                        break;
                    case "Defiant":
                        this.Ships.Add(new Defiant());
                        break;
                    case "Galaxy":
                        this.Ships.Add(new Galaxy());
                        break;
                    case "Galor":
                        this.Ships.Add(new Galor());
                        break;
                    case "Vor'cha":
                        this.Ships.Add(new Vorcha());
                        break;
                    default:
                        break;
                }
            }
        }

        public String GetName()
        {
            return FleetName;
        }

        public String getRoundReport(int round)
        {
            String roundReport = "";
            if (ShipsDestroyed != null) // Then a ship was destroyed
            {
                roundReport += "After round " + round.ToString() + " the " + FleetName + " fleet has lost" + "\r\n";
                foreach (BaseShip ship in ShipsDestroyed)
                    roundReport += "  " + ship.GetName() + " destroyed" + "\r\n";
                roundReport += "\r\n"; // Blank line for readability
            }
            return roundReport;
        }

        public String getDamageReport()
        {
            String damageReport = "";
            foreach (BaseShip ship in Ships)
                damageReport += "    " + ship.GetName() + " - " + ship.DamageLevel() + "\r\n";
            return damageReport;
        }

        public int ShipCount()
        {
            return Ships.Count;
        }

        public int ShipsLostCount()
        {
            return (this.NumberOfShips - this.Ships.Count);
        }

        private int CountDestroyedShips()
        {
            int counter = 0;
            foreach (BaseShip ship in Ships)
            {
                if (ship.isDestroyed() == true)
                    counter += 1;
            }
            return counter;
        }

        public void RemoveDestroyedShips()
        {
            int deadShipCount = CountDestroyedShips();

            if (deadShipCount > 0)
            {
                List<BaseShip> shipsClone = new List<BaseShip>(this.Ships); // Shallow copy of this.Ships
                this.ShipsDestroyed = new List<BaseShip>();

                foreach (BaseShip ship in shipsClone)
                {
                    if (ship.isDestroyed() == true)
                    {
                        this.Ships.Remove(ship);
                        this.ShipsDestroyed.Add(ship);
                    }
                }
            }
            else
                ShipsDestroyed = null;
        }

        public List<BaseShip> GetShips()
        {
            return this.Ships;
        }

        public BaseShip GetShip(int shipIndex)
        {
            return this.Ships[shipIndex];
        }

        public int GetStartingFleetSize()
        {
            return this.NumberOfShips;
        }

        public bool HasShipsAlive()
        {
            return this.Ships.Count > 0;
        }
    }
}